home *** CD-ROM | disk | FTP | other *** search
-
- MSTREAM
-
- Design Description and Notes
-
- The MSTREAM sample application is an example of how you can use the new
- midiStream API that's built into Windows 95 to play MIDI data with low
- latency and low processor overhead.
-
- The basic idea behind the implementation is to start the application and
- initialize the user interface. After that's done, the following events occur.
-
- When a user selects and opens a file
- ------------------------------------
- Open a MIDI output device (the Microsoft MIDI Mapper in this case)
- Open the file using buffered I/O and fill our buffers with data in MIDI stream
- format
- Prepare and queue the buffers using midiOutPrepareHeader and midiStreamOut
- Wait until the user decides to do something else
-
- When PLAY is selected
- ---------------------
- Call midiStreamRestart to un-pause the device and begin playback
- As buffers are returned, the callback function fills them with more data and
- sends them back to be played by calling midiStreamOut() again. IT IS
- IMPORTANT THAT YOU NOTE THAT IT IS NOT NECESSARY FOR YOU TO UNPREPARE AND
- PREPARE BUFFERS EVERY TIME THEY ARE RETURNED. This is only a waste of time.
- All you have to do is send them back into the subsystem with midiStreamOut().
- The API documentation is a bit confusing on this point.
-
- When PAUSE is selected
- ----------------------
- Call midiStreamPause() and wait for a PLAY or PAUSE to call midiStreamRestart().
- Note that pausing may make your audio sound kind of funny, since all notes are
- turned off and some note on events may be lost when playback is restarted.
-
- When STOP is selected
- ---------------------
- Call midiStreamStop(). Since the callback function is in another thread, we
- use a Win32 synchronization object, an event to block the main thread until
- the callback thread has received all buffers. When this happens, we know it's
- okay to go ahead and call midiStreamReset() and then to go ahead and free all
- our buffers. Currently, the device is also closed using midiStreamClose().
- Then, if we are resetting the file to playback starting at its beginning point
- the next time PLAY is hit, reopen the file by calling StreamBufferSetup(),
- which is the workhorse function for opening a file and initializing the
- the converter and the buffers.
-
- Note that the reason we must close and reopen the device is due to an apparent
- bug in the Multimedia System which causes undesireable playback once a device
- has been stopped. If you disable the code for closing the device, then the rest
- of the code will automatically know it does not have to reopen the device.
- However, the following may occur: After the first instance of playback followed
- by a midiStreamStop() and a midiStreamReset(), there will be a pause equal in
- length to the first playback period before playback begins once more with a call
- to midiStreamRestart().
-
- If Looped is selected
- ---------------------
- Notice that the converter has a little function in it called RewindConverter()
- which is called by ConvertToBuffer() if the bLooped variable is TRUE. This
- function resets the track state structures and performs most of the steps
- originally executed in ConvertInit() with the notable exception of opening the
- file and reading in file and track header data. It simply resets the tracks
- to their initial state.
-
-
- More on the buffering scheme
- ----------------------------
- Note that there is an OUT_BUFFER_SIZE and a BUFFER_TIME_LENGTH. The idea is
- that the converter counts ticks and calculates when it has put at least
- BUFFER_TIME_LENGTH milliseconds worth of events in the buffer. At this point,
- it returns to the caller with a "full" buffer. At the very worst, there will
- be as many events as can fit into OUT_BUFFER_SIZE bytes. Imagine that you have
- to MIDI files loaded into memory somehow and you want to switch between them in
- a hurry to correspond to some action the player performed like switching rooms.
- All you have to do is start filling the next buffer with new data (assuming
- they streams use similar patch sets, time division settings, etc.) and after
- (NUM_STREAM_BUFFERS-1)*BUFFER_TIME_LENGTH milliseconds, the music will switch
- over automatically. This theory is sort of illustrated by the tempo trackbar
- control in this sample. This control sets a flag which forces the converter
- code to start a new buffer, with the first event being a new tempo setting.
- The tempo setting is calculated as a relative increase with respect to the
- last real tempo event from the file. Of course, to implement the scheme
- mentioned above requires some modification to the converter code so that it
- will work with multiple MIDI files.
-
- Since the buffering scheme uses very small buffers, it is currently rather
- sensitive to heavy activity which may prevent it from completing processing
- in time. This can be solved by increasing the NUM_STREAM_BUFFERS constant,
- but you must make a trade-off between latency and playback stability.
-
-
- Known problems and possible improvements:
- -----------------------------------------
- It is more desireable to enumerate all possible MIDI output devices and then
- either allow the user to use a specific device, or choose one which has
- desireable capabilities. It is not recommended that you ship a product
- which is hard-coded to use the MIDI Mapper only. For more on enumerating
- MIDI output devices, see the MIDIPLYR sample application which is part of
- the Win32 SDK.
-
- Instead of using the BUFFER_TIME_LENGTH, it would be possible to handle the
- time signature META event in MIDI files and calculate the length of a
- measure of music. Then you could change buffers at the end of each measure,
- which would probably yield a smoother sounding transition. It may even be
- possible to define system-exclusive events or other such extensions to the
- MIDI converter code designed to provide your application with extra data
- about when to switch between buffers or do other processing, though it is
- not necessarily recommended that you modify the MIDI file format spec. For
- more information on that spec, contact the International MIDI Association.
-
- You may wish to modify the way a change in tempo is handled, or remove this
- code entirely. Right now, there is a chunk of code in the convert function
- AddEventToStreamBuffer() which detects tempo events and stores the new tempo.
- There is also code which will react to the tempo slider by calculating a new
- tempo, truncating the current buffer, and starting the next buffer with a
- tempo event reflecting the new desired tempo. It may be more desireable to
- force any tempo changes which are not encoded in the file originally to take
- effect only on buffer boundaries, instead of always creating a buffer
- boundary. Proceeding under the above context of buffers equal in length to
- measures of music, it may make more sense to only change tempo between each
- measure. You can also send tempo change messages using the midiOutShortMsg()
- function, similar to the way SetAllChannelVolumes() behaves.
-
- The volume control is a channel-wide, percentage-based control which relies on
- a cache of volumes for each channel. As the converter encounters a volume
- change message, it flags it for a callback. This causes the MidiProc() to
- receive notification when that event is reached. MidiProc() then grabs a
- copy of the new volume event and sends a MIDI short message to the proper
- channel which reflects the current slider position. In other words, the
- code saves the "full" or "raw" value and then modifies it so the volume
- trackbar represents a percentage of that volume. Though it is not shown
- here, this scheme could be broken down to allow for individual volume
- control also. This idea could also be expanded to include the LSB volume
- controller(39), which is not handled here.
-
- Further, by duplicating the volume code described above and making slight
- modifications to the converter (to detect other events), it is possible to
- handle pan, balance, or other controller messages using the exact same idea.
-
- Having said the above, it should be noted that attaching the volume change code
- to a trackbar is for illustration purposes only. The implementation shown
- and described works best for isolated volume events, like when your player
- moves away from the sound source and you need to update volume. It should
- not really be used for real-time scrolling because the method tends to flood
- the MIDI output device with short messages, which interferes severly with
- playback.
-
- BUG: If you are using an internal MIDI device which uses the OPL chipset, you
- should be aware of a bug which seems to occur in most of these drivers. If
- a volume channel message is sent to these drivers, they will not reflect the
- change until a note on/off event occurs. This means long sustaining notes
- will not reduce in volume.
-